package org.easystub;

import java.util.HashMap;
import java.util.Map;

public class RecordedInvocationAnswerer implements InvocationAnswerer {

    private final Map<Invocation, InvokableResult> invocationToReportedResult = new HashMap<Invocation, InvokableResult>();

    public boolean canAnswer(Invocation invocation) {
        return invocationToReportedResult.containsKey(invocation);
    }

    public Object answer(Invocation invocation) {
        return invocationToReportedResult.get(invocation).invoke();
    }

    public void record(Invocation invocation, Object result) {
        assertFirstRecordOf(invocation);
        invocationToReportedResult.put(invocation, new ObjectResult(result));
    }

    public void record(Invocation invocation, Throwable throwable) {
        assertFirstRecordOf(invocation);
        invocationToReportedResult.put(invocation, new ThrowableResult(throwable));
    }

    private void assertFirstRecordOf(Invocation invocation) {
        if(invocationToReportedResult.containsKey(invocation)) {
            throw new IllegalStateException("multiple answers to a stubbed method are not allowed. Only one call to EasyStub.stub(...).withValue() is allowed per method");
        }
    }

    public String toString() {
        if(invocationToReportedResult.isEmpty()) {
            return "no invocations reported";
        }
        
        StringBuilder builder = new StringBuilder();
        for(Map.Entry<Invocation, InvokableResult> entry : invocationToReportedResult.entrySet()) {
            if(entry.getValue() instanceof ThrowableResult) {
                describeThrowableInvocation(entry, builder);
            } else {
                describeObjectInvocation(entry, builder);
            }

        }
        return builder.substring(0, builder.lastIndexOf(", "));
    }

    private void describeObjectInvocation(Map.Entry<Invocation, InvokableResult> entry, StringBuilder builder) {
        Invocation invocation = entry.getKey();
            builder.append(String.format("method %s \n\twill return \n\t\t<<(%s) %s>>, \n",
                    invocation.getMethodCalled(),
                    entry.getValue().invoke().getClass().getName(),
                    entry.getValue().invoke()));
    }

    private void describeThrowableInvocation(Map.Entry<Invocation, InvokableResult> entry, StringBuilder builder) {
        Throwable throwable = null;
        try {
           entry.getValue().invoke();
        } catch (ThrowableResultExceptionWrapper wrapper) {
            throwable = wrapper.getResult();
        }

        Invocation invocation = entry.getKey();
            builder.append(String.format("method %s \n\twill throw \n\t\t<<%s>>, \n",
                    invocation.getMethodCalled(),
                    throwable.getClass().getName()));
    }


}
